package com.ecr.hub.service.hub.impl;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.mail.MessagingException;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ecr.hub.ApplicationException;
import com.ecr.hub.DaoApplicationException;
import com.ecr.hub.FunctionnalApplicationException;
import com.ecr.hub.MailNotSendException;
import com.ecr.hub.NumLotException;
import com.ecr.hub.RailNotAvailableException;
import com.ecr.hub.RailNotEmptyException;
import com.ecr.hub.RailNotFoundException;
import com.ecr.hub.SillonAlreadyProcededException;
import com.ecr.hub.SillonNotFoundException;
import com.ecr.hub.WagonAlreadyInFormationPlanException;
import com.ecr.hub.WagonAlreadyInTrafficException;
import com.ecr.hub.WagonNotFoundException;
import com.ecr.hub.WagonNumberException;
import com.ecr.hub.WagonPosTheoriqueException;
import com.ecr.hub.database.dao.IDaoComposition;
import com.ecr.hub.database.dao.IDaoHub;
import com.ecr.hub.database.dao.IDaoRail;
import com.ecr.hub.database.dao.IDaoSillon;
import com.ecr.hub.model.composition.Composition;
import com.ecr.hub.model.enumeration.HubBundleType;
import com.ecr.hub.model.enumeration.HubCurrentAction;
import com.ecr.hub.model.enumeration.HubLading;
import com.ecr.hub.model.enumeration.HubSillonStatus;
import com.ecr.hub.model.enumeration.HubSillonType;
import com.ecr.hub.model.enumeration.HubStack;
import com.ecr.hub.model.enumeration.HubWagonRiv;
import com.ecr.hub.model.enumeration.HubWagonStatus;
import com.ecr.hub.model.enumeration.HubWagonTrailer;
import com.ecr.hub.model.hub.HubTrafficController;
import com.ecr.hub.model.rail.Rail;
import com.ecr.hub.model.sillon.Sillon;
import com.ecr.hub.model.user.User;
import com.ecr.hub.model.wagon.Wagon;
import com.ecr.hub.model.wagon.WagonStatus;
import com.ecr.hub.model.wagon.WagonType;
import com.ecr.hub.service.hub.IHubManager;
import com.ecr.hub.service.impression.pdf.IPdfManager;
import com.ecr.hub.service.impression.pdf.impl.PdfManagerImpl;
import com.ecr.hub.service.mail.IMailManager;
import com.ecr.hub.util.LabelUtils;
import com.ecr.hub.util.MessageUtils;

@Service("hubManager")
public class HubManagerImpl implements IHubManager {

	// Le dao pour les ressources de type voie.
	@Resource(name = "daoRail")
	private IDaoRail daoRail;

	// Le dao pour les ressources de type sillon.
	@Resource(name = "daoSillon")
	private IDaoSillon daoSillon;

	// Le dao pour les ressources de type composition.
	@Resource(name = "daoComposition")
	private IDaoComposition daoComposition;

	// Le dao pour les resources concernant directement le hub.
	@Resource(name = "daoHub")
	private IDaoHub daoHub;

	// Le manager pour les documents pdf.
	@Resource(name = "pdfManager")
	private IPdfManager pdfManager;

	// Le manager pour les mails.
	@Resource(name = "mailManager")
	private IMailManager mailManager;

	// ******************************************
	// Partie : TrafficController.
	// ******************************************

	/**
	 * Récupération de la liste des voies actives pour affichage sur le panneau
	 * du Hub. Les voies sont triées par type de faisceau puis par ordre
	 * d'apparition dans le faisceau. Comme il s'agit d'une requête spécifique
	 * pour le hub, on ne fait pas appel au manager pour la gestion des voies
	 * mais directement au DAO pour les voies.
	 */
	@Override
	public HubTrafficController getHub() throws ApplicationException {

		try {
			HubTrafficController trafficController = new HubTrafficController(
					daoRail.getListForHub());
			refreshHub(trafficController);
			return trafficController;
		} catch (EmptyResultDataAccessException e) {
			return null;
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	// ******************************************
	// Partie : Sillon.
	// ******************************************

	/**
	 * Récupère la liste des sillons au départ du Hub. A utiliser pour le plan
	 * de formation et le relevé de composition.
	 */
	@Override
	public List<Sillon> checkDepartures() throws ApplicationException {

		try {
			return daoSillon.getListForDeparture();
		} catch (EmptyResultDataAccessException e) {
			// Laisse gérer par le datatable de Jsf.
			return null;
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupère la liste des sillons à l'arrivée sur le Hub. A utiliser pour la
	 * gestion des réception, des plans de coupe et des relevés de composition.
	 */
	@Override
	public List<Sillon> checkArrivals() throws ApplicationException {

		try {
			return daoSillon.getListForArrival();
		} catch (EmptyResultDataAccessException e) {
			// Laisse gérer par le datatable de Jsf.
			return null;
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Mise à jour du sillon avec un commentaire (que cela soit un sillon à
	 * l'arrivée ou au départ).
	 */
	@Override
	public void updateSillonComment(Sillon sillon) throws ApplicationException {
		try {
			daoSillon.updateComment(sillon);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Permet d'avoir le prochain statut d'un sillon en fonction de son statut
	 * actuel. On met directement le sillon à jour avec l'identifiant de son
	 * nouveau statut.
	 */
	private void getNextSillonStatus(Sillon sillon,
			HubCurrentAction currentAction) {

		// Réception du train.
		if (currentAction == HubCurrentAction.PROCESS_ARRIVAL) {
			if (sillon.getStatusId() == HubSillonStatus.COMPO_PLAN_ARRIVED_NO_TRAIN
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.COMPO_PLAN_ARRIVED_TRAIN
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.WORKING_PLAN_REQUESTED_NO_TRAIN
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.WORKING_PLAN_REQUESTED_TRAIN
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.WORKING_PLAN_EXCLUDED
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.ARRIVAL_COMPLETED.getValue());
			}
		}

		// Demande du plan de travail
		if (currentAction == HubCurrentAction.PROCESS_WORKIN_PLAN) {
			if (sillon.getStatusId() == HubSillonStatus.COMPO_PLAN_ARRIVED_NO_TRAIN
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.WORKING_PLAN_REQUESTED_NO_TRAIN
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.COMPO_PLAN_ARRIVED_TRAIN
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.WORKING_PLAN_REQUESTED_TRAIN
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.WORKING_PLAN_REQUESTED_TRAIN
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.WORKING_PLAN_PROCESSING_TRAIN
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.WORKING_PLAN_PROCESSING_TRAIN
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.ARRIVAL_COMPLETED.getValue());
			}
		}

		// Demande du programme de formation.
		if (currentAction == HubCurrentAction.PROCESS_FORMATION) {
			if (sillon.getStatusId() == HubSillonStatus.ATTRIBUTED.getValue()) {
				sillon.setStatusId(HubSillonStatus.FORMATION_PLAN_PROCESSING
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.FORMATION_PLAN_PROCESSING
					.getValue()) {
				// Si la voie de départ == voie de formation alors on passe
				// directement au statut "DEPARTURE_READY".
				if (sillon.getRailId() == sillon.getRailFormationId()) {
					sillon.setStatusId(HubSillonStatus.DEPARTURE_READY
							.getValue());
				} else {
					sillon.setStatusId(HubSillonStatus.FORMATION_PLAN_COMPLETED
							.getValue());
				}
			}
		}

		// Gestion du départ et du relevé de composition.
		if (currentAction == HubCurrentAction.PROCESS_DEPARTURE) {
			if (sillon.getStatusId() == HubSillonStatus.FORMATION_PLAN_COMPLETED
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.DEPARTURE_PROCESSING
						.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.DEPARTURE_PROCESSING
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.DEPARTURE_READY.getValue());
			} else if (sillon.getStatusId() == HubSillonStatus.DEPARTURE_READY
					.getValue()) {
				sillon.setStatusId(HubSillonStatus.DEPARTURE_COMPLETED
						.getValue());
			}
		}
	}

	/**
	 * Méthode pour retrouver un sillon à partir de son identifiant externe et
	 * de sa date.
	 */
	@Override
	public Sillon getSillonByExtIdAndDate(String sillonExtId, Date dateSillon)
			throws ApplicationException {

		try {
			return daoSillon.getByExtIdAndDate(sillonExtId, dateSillon);
		} catch (EmptyResultDataAccessException e) {
			throw new SillonNotFoundException("Hub : Aucun sillon à récupérer.");
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode pour retrouver un sillon à partir du train (tcms train id).
	 */
	@Override
	public Sillon getSillonByTcmsTrainId(String tcmsTrainId)
			throws ApplicationException {

		try {
			return daoSillon.getByTcmsTrainId(tcmsTrainId);
		} catch (EmptyResultDataAccessException e) {
			throw new SillonNotFoundException("Hub : Aucun sillon à récupérer.");
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode pour retrouver une liste de sillons à partir d'une date pour le
	 * sillon et (éventuellement) une plage horaire. On récupère l'ensemble des
	 * sillons, qu'il s'agisse d'un sillon de départ ou d'arrivée, ou uniquement
	 * les sillon de départ, ou d'arrivée.
	 */
	@Override
	public List<Sillon> getListSillonsByDateAndTime(Date date, Date dateEnd,
			String time, String timeEnd, String type)
			throws ApplicationException {

		// Si aucune plage horaire, on positionne la plage automatiquement.
		// A voir si cela sert de les passer en constantes...
		if (time == null || time.length() != 5) {
			time = "00:00";
			timeEnd = "23:59";
		}

		try {
			return daoSillon.getListByDateAndTime(date, dateEnd, time, timeEnd,
					type);
		} catch (EmptyResultDataAccessException e) {
			throw new SillonNotFoundException(
					"Hub : Aucune liste de sillon à récupérer.");
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode pour supprimer un sillon. dans tous les cas, on passe le statut
	 * du sillon à "Annulé". Par contre, si le sillon est de type "Arrivée", il
	 * faut regarder si un programme de travail a déjà été effectué en
	 * prévisionnel et si oui, on supprime l'ensemble des wagons du hub (Idem
	 * pour un train reçu par erreur).
	 */
	@Override
	@Transactional
	public void deleteSillon(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		try {

			boolean wagonErased = false;
			List<Wagon> lstWagons = null;

			// Mise à jour du statut sillon.
			sillon.setStatusId(HubSillonStatus.CANCELED.getValue());

			try {
				lstWagons = daoHub.getListWagonTrafficComposition(sillon);
			} catch (EmptyResultDataAccessException e) {
				lstWagons = new ArrayList<Wagon>(0);
			}
			// Les arrivées.
			if (HubSillonType.ARRIVAL.getValue().equals(sillon.getType())) {
				// Si présence de wagons, alors on les supprime du hub.
				for (Wagon wagon : lstWagons) {
					trafficController.deleteWagon(wagon);
					wagonErased = true;
				}
				// Les départs
			} else {
				// Suppression des wagons théoriques.
				for (Wagon wagon : lstWagons) {
					if (!wagon.getCurrentPos().isPosReal()) {
						trafficController.deleteWagon(wagon);
						wagonErased = true;
					}
				}
				// Remise en disponibilité des voies.
				initDepartureRail(trafficController, sillon);
			}
			// Mise à jour du traffic.
			if (wagonErased)
				updateTraffic(trafficController);

			// Mise à jour du sillon.
			daoSillon.update(sillon);
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode pour réinitialiser un sillon. On repositionne le sillon à son
	 * dernier statut avant les différentes opérations effectuées par le
	 * coordinateur, on efface toutes les opérations antérieures. Par ailleur
	 * pour les sillons à l'arrivée, on remet la composition avec les voies
	 * initiales grâce au champ de sauvegarde de la valeur de la voie(dans le
	 * cas ou le coordinateur à modifié des voies pour certains lots de wagon).
	 */
	@Override
	@Transactional
	public void initSillon(HubTrafficController trafficController, Sillon sillon)
			throws ApplicationException {

		try {
			boolean wagonErased = false;
			List<Wagon> lstWagons = null;
			try {
				// Si présence de wagons, alors on les supprime du hub.
				lstWagons = daoHub.getListWagonTrafficComposition(sillon);
			} catch (EmptyResultDataAccessException e) {
				lstWagons = new ArrayList<Wagon>(0);
			}

			// Les arrivées.
			if (sillon.getType().equals(HubSillonType.ARRIVAL.getValue())) {
				sillon.setStatusId(HubSillonStatus.COMPO_PLAN_ARRIVED_NO_TRAIN
						.getValue());

				sillon.setRailLabel("");
				sillon.setRailExtId("");
				sillon.setRailId(0);

				// On remet la composition à son état initial.
				List<Composition> lstCompositions = daoHub.getPlan(sillon);
				for (Composition composition : lstCompositions) {
					composition.getWagon().getNextPos()
							.setPosRailId((composition.getPosRailSaveId()));
					composition.setComment("");
				}
				// Réinitialisation du programme de travail.
				daoHub.updateWorkingPlan(lstCompositions);

				// On efface tous les wagons (théoriques et réels) dans le
				// traffic.
				for (Wagon wagon : lstWagons) {
					trafficController.deleteWagon(wagon);
					wagonErased = true;
				}
				// Les départs.
			} else {

				// On supprime la composition.
				daoComposition.deleteAll(sillon.getId());

				// On efface tous les wagons (théoriques uniquement) dans le
				// traffic.
				for (Wagon wagon : lstWagons) {
					if (!wagon.getCurrentPos().isPosReal()) {
						trafficController.deleteWagon(wagon);
						wagonErased = true;
					}
				}

				// Remise en disponibilité des voies.
				initDepartureRail(trafficController, sillon);

				// Remet les voies à blanc.
				sillon.setRailExtId("");
				sillon.setRailFormationExtId("");
				sillon.setRailFormationLabel("");
				sillon.setRailFormationId(0);
				sillon.setRailId(0);

				// On remet le sillon en état initial.
				sillon.setStatusId(HubSillonStatus.ATTRIBUTED.getValue());
			}
			// Si inversion de sens demandé alors on refait l'inversion.
			if (sillon.isReverseSense()) {
				daoHub.updateReverseSense(sillon);
				sillon.setReverseSense(false);
			}
			// On remet la date et l'heure.
			sillon.setRealMovementDate(sillon.getMovementDate());
			sillon.setRealMovementTime(sillon.getMovementTime());

			// Mise à jour du traffic.
			if (wagonErased)
				updateTraffic(trafficController);
			// Mise à jour du sillon.
			daoSillon.update(sillon);
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode juste utilisée pour un minimum de factorisation de code, elle
	 * demande la remise en disponibilité des voies éventellements rendues
	 * indisponibles par le départ d'un train.
	 */
	@Transactional
	private void initDepartureRail(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		try {

			Rail departureRail;
			if (sillon.getRailFormationId() > 0) {
				// On remet la voie disponible (formation).
				departureRail = trafficController.getRailById(sillon
						.getRailFormationId());
				departureRail.setAvailable(true);
				daoRail.update(departureRail);
			}

			// Si voie de départ != voie formation alors on la rend
			// disponible.
			if (sillon.getRailId() > 0
					&& sillon.getRailId() != sillon.getRailFormationId()) {
				departureRail = trafficController.getRailById(sillon
						.getRailId());
				departureRail.setAvailable(true);
				daoRail.update(departureRail);
			}
		} catch (DataAccessException e) {
			throw new ApplicationException(e);
		}
	}

	// ******************************************
	// Partie : Programme de travail.
	// ******************************************

	/**
	 * Récupération du programme de travail. On récupère l'ensemble des
	 * enregistrements pour le relevé de composition. Comme il s'agit d'une
	 * requête multi-tables, on ne crée pas de manager pour les compositions, on
	 * considère qu'il s'agit d'une requête spécifique pour le Hub et on la fait
	 * passer par le DAO du Hub et non par celui des compositions qui est
	 * utilisé uniquement pour les opérations basiques sur la table hub_compo.
	 * On vérifie avec la composition que tous les wagons peuvent bien êtres
	 * positionnés. On Positionne le statut des wagons selon que le train soit
	 * arrivé ou non. S'il y a une erreur dans le trafficController, il faut le
	 * rafraichir pour revenir à la situation initiale.
	 * 
	 * On vérifie si la voie est disponible, si elle ne l'est pas on recherche
	 * si autre voie peut correspondre. On est obligé de faire ce test ici et
	 * non au momment de la création par le fichier car, entre le momment de la
	 * création initiale de la composition et le momment ou le coordinateur
	 * affecte le programme de travail, le statut de la voie peut avoir changé.
	 */
	@Override
	public List<Composition> getWorkingPlan(
			HubTrafficController trafficController, Sillon sillon)
			throws ApplicationException {

		try {
			String strFromRail;
			List<Composition> lstComposition = daoHub.getPlan(sillon);

			// Récupération du statut wagon.
			int wagonStatusId = (sillon.getStatusId() == HubSillonStatus.COMPO_PLAN_ARRIVED_NO_TRAIN
					.getValue()) ? HubWagonStatus.WAGON_NOT_ARRIVED.getValue()
					: HubWagonStatus.WAGON_ARRIVED.getValue();

			// Récupération de la voie de reception en String si remplie.
			strFromRail = trafficController.getRailDesignation(sillon
					.getRailId());

			// Positionnement de chaque wagon dans le hub.
			for (Composition composition : lstComposition) {
				try {
					// Récupération de la voie.
					Rail rail = trafficController.getRailById(composition
							.getWagon().getNextPos().getPosRailId());

					// Si la voie est indisponible on cherche si une autre voie
					// correspond pour la même destination.
					if (!rail.isAvailable())
						getOtherRailForWagon(trafficController,
								composition.getWagon());

					composition.getWagon().setFromRail(strFromRail);
					composition.getWagon().getCurrentStatus()
							.setId(wagonStatusId);
					// Placement du wagon dans le traffic.
					trafficController.moveWagon(composition.getWagon(),
							HubStack.POS_END, false);
				} catch (FunctionnalApplicationException e) {
					composition.getWagon().setErrorMessage(e.getMessage());
				}
			}
			return lstComposition;
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupération du programme de travail, programme de formation, etc.... On
	 * récupère l'ensemble des enregistrements pour le relevé de composition. A
	 * la différence de la méthode précédente plus complexe, ici on ne fait pas
	 * la répartition des wagons dans le hub (trafficController), on se contente
	 * juste de récupérer les données de la base. Cette méthode sert nottament
	 * pour les impressions (à partir du menu "Impressions").
	 */
	@Override
	public List<Composition> getPlan(Sillon sillon) throws ApplicationException {

		try {
			return daoHub.getPlan(sillon);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Mise à jour dans la base du programme de travail. 1) on modifie le statut
	 * du sillon qui passe à "Plan de travail demandé", on met à jour les voies
	 * (éventuellement modifiées) dans la liste des compositions, et on flush le
	 * hub dans la base de données. On vérifie que toutes les voies so nt
	 * disponibles.
	 */
	@Override
	@Transactional
	public void requestWorkingPlan(HubTrafficController trafficController,
			Sillon sillon, List<Composition> lstCompositions)
			throws ApplicationException {

		try {
			// On balaye la liste des compositions et on vérifie que les voies
			// sont bien disponibles. (Il ne faut pas que le coordinateur passe
			// au dela des messages affichés au niveau du plan de travail en ce
			// qui concerne la validité de la voie).
			int railId = 0;
			for (Composition composition : lstCompositions) {
				if (railId != composition.getWagon().getNextPos()
						.getPosRailId()) {
					railId = composition.getWagon().getNextPos().getPosRailId();
					trafficController.checkRailAvailable(railId);
				}
				// On en profite pour mettre à jour les wagons dans le traffic
				// avec le commentaire saisi pour le wagon dans la composition.
				composition.getWagon().setComment(composition.getComment());
				trafficController.updateWagonForComment(composition.getWagon());
			}
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_WORKIN_PLAN);
			updateTraffic(trafficController);
			daoHub.updateWorkingPlan(lstCompositions);
			daoSillon.update(sillon);
		} catch (RailNotAvailableException e) {
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode qui permet de passer le statut des wagons pour un train à
	 * "En cours de tri". Il est impossible de passer ce statut si le train
	 * n'est pas encore arrivé (bloqué en amont par l'icône qui est désactivée).
	 */
	@Override
	@Transactional
	public void processWorkingPlan(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		try {
			List<Wagon> lstWagons = daoHub
					.getListWagonTrafficComposition(sillon);
			for (Wagon wagon : lstWagons) {
				wagon.getNextStatus().setId(
						HubWagonStatus.WAGON_SORT_PROCESSING.getValue());
				// On distingue la voie de reception et les voies de tri.
				if (sillon.getRailId() != wagon.getCurrentPos().getPosRailId()) {
					trafficController.updateWagonStatus(wagon, false);
				} else {
					trafficController.updateWagonStatus(wagon, true);
				}
			}
			updateTraffic(trafficController);
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_WORKIN_PLAN);
			daoSillon.update(sillon);

		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * On place le statut des wagons à "Trié" et on efface les wagons sur la
	 * voie de réception. On récupère la voie de reception, la liste des wagons
	 * dans le hub, pour tous ceux qui sont sur la voie de reception, on les
	 * supprime, pour les autres, on passe le statut à "Trié". Enfin on passe le
	 * statut du sillon à "Arrivée complétée".
	 */
	@Override
	public void completeWorkingPlan(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {
		// Récupération de la voie de reception.
		int railId = sillon.getRailId();
		try {
			List<Wagon> lstWagons = daoHub
					.getListWagonTrafficComposition(sillon);
			for (Wagon wagon : lstWagons) {
				// Si wagon sur reception : suppression.
				if (wagon.getCurrentPos().getPosRailId() == railId) {
					trafficController.deleteWagon(wagon);
					// Sinon : modification statut.
				} else {
					wagon.getNextStatus().setId(
							HubWagonStatus.WAGON_SORT_COMPLETED.getValue());
					trafficController.updateWagonStatus(wagon, true);
				}
			}
			updateTraffic(trafficController);
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_WORKIN_PLAN);
			daoSillon.update(sillon);
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * On affiche le document pdf pour le programme de travail. Bien que l'on
	 * ait la liste des compositions, on préfère ne conserver que le sillon et
	 * rechercher en base la liste pour deux raisons : cela permet de vérifier
	 * la prise en compte des données éventuellement modifiées par le
	 * coordinateur et permet de récupérer plus facilement les infos concernant
	 * la voie.
	 */
	@Override
	public String displayWorkingPlanPdf(User user, Sillon sillon)
			throws ApplicationException {

		try {
			List<Composition> lstCompositions = daoHub.getPlan(sillon);
			return pdfManager.createPdfWorkingPlan(user, lstCompositions,
					sillon);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	// ******************************************
	// Partie : Reception du train.
	// ******************************************

	/**
	 * Cette méthode permet de gérer l'arrivée d'un train sur le hub. On
	 * récupère la composition du train à partir du sillon, on place les wagons
	 * sur la voie de réception. Comme le placement des wagons a déjà été testé
	 * lors du choix de la voie, on ne fait rien si on récupère des erreurs
	 * fonctionnelles. On teste si le programme de travail a déjà été effectué,
	 * si oui, alors il faut passer le statut des wagons à "Arrivé Gevrey". Pour
	 * les rames vides, le statut passe directement à "Arrivée complétée" car il
	 * n'y a pas besoin de faire le programme de travail.
	 */
	@Override
	@Transactional
	public void completeArrival(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		// Vérifie le statut du sillon pour savoir si on doit mettre à jour
		// le statut des wagons dans le programme de travail.
		boolean updateStatus = (sillon.getStatusId() == HubSillonStatus.WORKING_PLAN_REQUESTED_NO_TRAIN
				.getValue()) ? true : false;

		// On vérifie que la voie est disponible
		trafficController.checkRailAvailable(sillon.getRailId());

		// Positionne le numero de la voie de reception dans le sillon (extId).
		Rail arrivalRail = trafficController.getRailById(sillon.getRailId());
		sillon.setRailExtId(arrivalRail.getNumber());

		// Positionne le libellé de la voie dans le sillon.
		sillon.setRailLabel((arrivalRail.getTypeBundle().getId() == HubBundleType.SORT
				.getValue()) ? arrivalRail.getDestinationLabel() : arrivalRail
				.getTypeBundle().getLabel());

		try {
			List<Composition> lstCompositions = daoHub.getPlan(sillon);
			for (Composition composition : lstCompositions) {

				Wagon wagon = composition.getWagon();
				wagon.getCurrentStatus().setId(
						HubWagonStatus.WAGON_ARRIVED.getValue());

				// On place le wagon dans la demande de futur
				// positionnement.
				wagon.getNextPos().setPosRailId(sillon.getRailId());
				// On déplace le wagon.
				trafficController.moveWagonAExOnly(wagon, HubStack.POS_END,
						true);

				// Si programme de travail déjà fait, modification de
				// statut. On récupère la liste des wagons dans le hub pour la
				// composition à traiter (+ simple car on a les vraies positions
				// en base). De plus on positionne la voie de reception.
				if (updateStatus) {
					// On récupère la chaine pour la voie (comme on a déjà fait
					// la mointiée du travail plus haut on appelle pas la
					// méthode "getStringRailDesignation" du trafficController).
					StringBuffer fromRail = new StringBuffer(
							sillon.getRailExtId()).append(" (");
					fromRail.append(sillon.getRailLabel()).append(")");

					List<Wagon> lstWagon = daoHub
							.getListWagonTrafficComposition(sillon);
					for (Wagon hubWagon : lstWagon) {
						// On positionne le statut.
						hubWagon.getNextStatus().setId(
								HubWagonStatus.WAGON_ARRIVED.getValue());
						// On positionne la voie d'arrivée pour info.
						hubWagon.setFromRail(fromRail.toString());
						// Mise à jour du statut.
						trafficController.updateWagonStatus(hubWagon, false);
						// Mise à jour de la voie de provenance.
						trafficController.updateWagonFromRail(hubWagon);
					}
				}
			}
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_ARRIVAL);
			updateTraffic(trafficController);
			daoSillon.update(sillon);
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	// *******************************************
	// Partie : Formation.
	// *******************************************

	/**
	 * Cette méthode permet de préparer l'affichage de l'écran de gestion des
	 * trains à formation. Afin d'aider le coordinateur dans sa décision, on lui
	 * donne le nombre de wagons en position réelle pour chaque voie. Plutôt que
	 * de gérer ce nombre en temps réel, on le calcule dans cette fonction. On
	 * réinitialise aussi les données (nbWagonStack et numLot) dans chaque rail.
	 * On vérifie que la voie est disponible.
	 */
	@Override
	public void getFormationPlanPhaseOne(
			HubTrafficController trafficController, Sillon sillon)
			throws ApplicationException {

		int nbPosReal;
		trafficController.checkRailAvailable(sillon.getRailFormationId());
		for (Rail trafficRail : trafficController.getLstRails()) {
			trafficRail.reinitErrorIndicator();
			trafficRail.setNbWagonsStack(0);
			trafficRail.setNumlot(0);
			nbPosReal = 0;
			for (Wagon wagon : trafficRail.getLstWagons()) {
				if (wagon.getCurrentPos().isPosReal())
					nbPosReal++;
			}
			trafficRail.setNbRealPos(nbPosReal);
		}
	}

	/**
	 * Préparation du plan de composition (Phase 2). On récupère l'ensemble des
	 * wagons à dépiler et on les place sur la voie de formation choisie par le
	 * coordinateur. On liste la demande de formation et pour chaque rail on
	 * récupère la liste des wagons que l'on dépile (toujours par la droite). On
	 * crée dès maintenant le programme de formation car le coordinateur peut
	 * décider d'envoyer des wagons sur une voie ou il existe déjà des wagons
	 * qui ne font pas partie du programme de travail. On ne peut donc se fier à
	 * la photo de la voie pour créer le programme de travail. Par contre, la
	 * liste des compositions n'est pas encore insérée en base (uniquement sur
	 * la validation de l'écran de phase 2).
	 */
	@Override
	@Transactional
	public List<Composition> getFormationPlanPhaseTwo(
			HubTrafficController trafficController, Sillon sillon)
			throws ApplicationException {

		int numLotOnForm = 0;
		int posNumOffset = 0;
		int posNum = 0;
		boolean bError = false;
		Map<Integer, Rail> lstMapFormations = new HashMap<Integer, Rail>();
		ArrayList<Composition> lstCompositions = new ArrayList<Composition>();

		// On vérifie les données et on récupère les voies pour le dépilage des
		// wagons (place les voies dans une map).
		for (Rail rail : trafficController.getLstRails()) {
			rail.reinitErrorIndicator();
			if (rail.getNbWagonsStack() > 0) {
				if (rail.getNbRealPos() < rail.getNbWagonsStack()) {
					bError = true;
					rail.setErrorMessage(MessageUtils
							.getStringMessageError("hub.error.nbwagons"));
				}
				if (lstMapFormations.containsKey(new Integer(rail.getNumlot()))) {
					bError = true;
					rail.setErrorMessage(MessageUtils
							.getStringMessageError("hub.error.numlot.weak"));
				}
				if (rail.getNumlot() < 1) {
					bError = true;
					rail.setErrorMessage(MessageUtils
							.getStringMessageError("hub.error.numlot.required"));
				}
				try {
					trafficController.checkWagonsPosReal(rail.getId(),
							rail.getNbWagonsStack());
				} catch (WagonPosTheoriqueException e) {
					bError = true;
					rail.setErrorMessage(MessageUtils
							.getStringMessageError("hub.error.wagons.theo"));
				}
				// On en profite pour récupérer le numéro de lot de la voie de
				// formation (si existe).
				if (rail.getId() == sillon.getRailFormationId())
					numLotOnForm = rail.getNumlot();

				// Insertion de la voie dans la map avec le lot en clé.
				lstMapFormations.put(new Integer(rail.getNumlot()), rail);
			}
		}

		// Interdiction d'empilement à gauche avec un lot partiel
		// sur la voie de formation.
		Rail railFormation = lstMapFormations.get(numLotOnForm);
		if (railFormation != null
				&& numLotOnForm > 0
				&& numLotOnForm < lstMapFormations.size()
				&& railFormation.getNbWagonsStack() < railFormation
						.getLstWagons().size()) {
			bError = true;
			railFormation.setErrorMessage(MessageUtils
					.getStringMessageError("hub.error.numlot.nbwagons"));
		}

		if (bError)
			throw new ApplicationException(
					"Hub : Des erreurs ont été trouvées "
							+ "dans la demande de formation");

		if (lstMapFormations.size() == 0)
			throw new RailNotFoundException(
					"Hub : Aucune voie n'a été selectionnée");

		// On effectue le déplacement des wagons sur la voie de formation (si on
		// arrive ici, cela signifie qu'il n'y a pas d'erreur basique on doit
		// donc pouvoir itérer sur les numéros de lot, si problème cela signifie
		// qu'il manque des lots (rafraichissement du hub). On envoi les wagons
		// sur la voie de formation et on crée le programme de formation.
		try {
			for (int i = 0; i < lstMapFormations.size(); i++) {
				Rail rail = lstMapFormations.get(new Integer(i + 1));

				// A ce niveau on peut vérifier s'il manque des lots.
				if (rail == null)
					throw new NumLotException(
							"Hub : Il manque un numéro de lot");

				// Itération sur la liste des wagons, on les envoi sur la voie
				// de formation. On crée le programme de travail.
				for (int ii = 0; ii < rail.getNbWagonsStack(); ii++) {

					// Si on agit sur la voie de formation alors il est possible
					// que l'on ait des lots avant les wagons déjà présents, il
					// faut donc prendre un offset en compte pour dépiler le lot
					// qui est déjà présent sur la voie de formation.
					int idx = (rail.getId() == sillon.getRailFormationId()) ? ii
							+ posNumOffset
							: ii;

					Wagon wagon = rail.getLstWagons().get(idx);
					wagon.getCurrentStatus().setId(
							HubWagonStatus.WAGON_FORMING_PROCESSING.getValue());

					wagon.setTcmsTrainId(sillon.getTcmsTrainId());
					wagon.setSillonExtId(sillon.getExtId());
					wagon.setSillonId(sillon.getId());
					// Très important !!!! Ici on part du rail et non de la
					// liste des compositions donc clonner le wagon !!!!
					Wagon formWagon = (Wagon) wagon.clone();
					formWagon.getNextPos().setPosRailId(
							sillon.getRailFormationId());
					formWagon.setNumLot(rail.getNumlot());

					// On ne fait rien si le wagon est déjà positionné sur sa
					// voie. (il faut voir pour les autres si on les place avant
					// ou après.
					if (sillon.getRailFormationId() != rail.getId()) {
						if (numLotOnForm == 0
								|| (rail.getNumlot() > numLotOnForm)) {
							// Ne pas oublier que moveWagon fait un clone du
							// wagon...
							trafficController.moveWagonAExOnly(formWagon,
									HubStack.POS_END, false);
						} else {
							formWagon.getNextPos().setPosNum(posNum);
							trafficController.moveWagonAExOnly(formWagon,
									HubStack.POS_AT, false);
							posNumOffset++;
						}
					}
					formWagon.getCurrentPos().setPosNum(posNum);
					Composition composition = new Composition();
					composition.setRailNumber(rail.getNumber());
					composition.setPosRailId(rail.getId());
					composition.setSillonId(sillon.getId());
					composition.setPosRailSaveId(rail.getId());
					composition.setWagon(formWagon);
					// Pour l'affichage on place aussi des infos voie.
					composition.setDestinationHexColor(rail
							.getDestinationHexColor());
					composition.setDestinationTypeBundleId(rail.getTypeBundle()
							.getId());

					String strLabel = (rail.getTypeBundle().getId() == HubBundleType.SORT
							.getValue()) ? rail.getDestinationLabel() : rail
							.getTypeBundle().getLabel();

					composition.setDestinationLabel(strLabel);
					lstCompositions.add(composition);
					posNum++;
				}
			}
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
		// A ce niveau, on a notre liste des compositions.
		return lstCompositions;
	}

	/**
	 * Méthode qui, à validation dans l'écran phase 2 du processus, va écrire la
	 * composition dans la table hub_compo, on met à jour le sillon avec le
	 * statut "Formation en cours". Une fois le sillon mis à jour, on crée le
	 * document pdf pour le programme de travail à partir des données
	 * enregistrées en base. On rend la voie de formation indisponible pour
	 * éviter le positionnement des wagons qui viendraient fausser le plan de
	 * formation.
	 */
	@Override
	@Transactional
	public void processFormationPlan(HubTrafficController trafficController,
			List<Composition> lstCompositions, Sillon sillon)
			throws ApplicationException {

		try {
			int nbEmpty = 0;
			int nbLoaded = 0;
			// Rend la voie indisponible.
			Rail formationRail = trafficController.getRailById(sillon
					.getRailFormationId());
			formationRail.setAvailable(false);
			daoRail.updateForAvailable(formationRail);
			// Insertion de la composition et mise à jour du sillon.
			daoComposition.insert(lstCompositions);
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_FORMATION);
			sillon.setNbWagons(lstCompositions.size());
			// Empty / Loaded
			for (Composition composition : lstCompositions) {
				if (HubLading.LOADED.getValue().equals(
						composition.getWagon().getLading())) {
					nbLoaded++;
				} else {
					nbEmpty++;
				}
			}
			// Mise à jour du sillon pour le chargement.
			if (nbEmpty > 0 && nbLoaded > 0) {
				sillon.setLading(HubLading.MIXED.getValue());
			} else if (nbLoaded > 0) {
				sillon.setLading(HubLading.LOADED.getValue());
			} else {
				sillon.setLading(HubLading.EMPTY.getValue());
			}
			updateTraffic(trafficController);
			daoSillon.update(sillon);
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * On affiche le document pdf pour le programme de formation. Bien que l'on
	 * ait la liste des compositions, on préfère ne conserver que le sillon et
	 * rechercher en base la liste pour deux raisons : cela permet de vérifier
	 * la prise en compte des données éventuellement modifiées par le
	 * coordinateur et permet de récupérer plus facilement les infos concernant
	 * la voie.
	 */
	@Override
	public String displayFormationPlanPdf(User user, Sillon sillon)
			throws ApplicationException {

		try {
			List<Composition> lstCompositions = daoHub.getPlan(sillon);
			return pdfManager.createPdfFormationPlan(user, lstCompositions,
					sillon);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode pour passer les statuts des wagons en cours de formation à
	 * "Formé". On passe tous les wagons en position théorique à une position
	 * réelle et on supprime tous les wagons en position théorique.
	 */
	@Override
	public void completeFormationPlan(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		// Récupération de la voie de formation.
		int railId = sillon.getRailFormationId();
		try {
			List<Wagon> lstWagons = daoHub
					.getListWagonTrafficComposition(sillon);
			for (Wagon wagon : lstWagons) {
				// Si wagon sur reception : modification statut.
				if (wagon.getCurrentPos().getPosRailId() == railId) {
					wagon.getNextStatus().setId(
							HubWagonStatus.WAGON_FORMING_COMPLETED.getValue());
					trafficController.updateWagonStatus(wagon, true);
					// Sinon : suppression.
				} else {
					trafficController.deleteWagon(wagon);
				}
			}
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_FORMATION);
			updateTraffic(trafficController);
			daoSillon.update(sillon);
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}

	}

	// *******************************************
	// Partie : Départ du train.
	// *******************************************

	/**
	 * Méthode pour positionner les wagons sur la voie de départ. On prend
	 * l'ensemble des wagons dans le plan de composition sur la voie de
	 * formation et on les place sur la voie de départ en position théorique. Le
	 * statut des wagons passe à "En cours de départ".
	 */
	@Override
	@Transactional
	public void requestDeparture(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		// Récupération du rail de départ.
		Rail departureRail = trafficController.getRailById(sillon.getRailId());

		try {
			List<Wagon> lstWagons = daoHub
					.getListWagonTrafficComposition(sillon);
			for (Wagon wagon : lstWagons) {
				wagon.getNextPos().setPosRailId(departureRail.getId());
				wagon.getNextStatus().setId(
						HubWagonStatus.WAGON_DEPARTURE_PROCESSING.getValue());
				trafficController.updateWagonStatus(wagon, true);
				// Ne pas oublier que "updateWagonStatus" n'agit pas sur
				// l'occurence du wagon que l'on envoi dans la fonction...
				wagon.getCurrentStatus().setId(
						HubWagonStatus.WAGON_DEPARTURE_PROCESSING.getValue());
				trafficController.moveWagonAExOnly(wagon, HubStack.POS_END,
						false);
			}
			// On rend le rail de départ indisponible.
			departureRail.setAvailable(false);
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_DEPARTURE);
			daoRail.updateForAvailable(departureRail);
			updateTraffic(trafficController);
			daoSillon.update(sillon);

		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Méthode pour changer le statut des wagons pour le déplacement sur la voie
	 * de départ à partir de la voie de formation. On place tous les wagons sur
	 * la voie de départ en position réelle avec le statut "Prêt au départ" et
	 * on met le sillon à jour. On libère la voie de formation s'il y a eu un
	 * déplacement (voie de formation != voie de départ).
	 */
	@Override
	@Transactional
	public void processDeparture(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		// Vérifie si la voie de formation == voie de départ.
		boolean otherDepartureRail = (sillon.getRailId() != sillon
				.getRailFormationId()) ? true : false;
		try {
			// Récupération de la liste des wagons.
			List<Wagon> lstWagons = daoHub
					.getListWagonTrafficComposition(sillon);
			for (Wagon wagon : lstWagons) {
				// On passe le statut des wagons à "Prêt au départ".
				wagon.getNextStatus().setId(
						HubWagonStatus.WAGON_DEPARTURE_READY.getValue());
				// Il faut supprimer les wagons de la voie de formation si la
				// voie de formation est différente de celle de départ.
				if (otherDepartureRail) {
					if (wagon.getCurrentPos().getPosRailId() == sillon
							.getRailFormationId()) {
						trafficController.deleteWagon(wagon);
					} else {
						trafficController.updateWagonStatus(wagon, true);
					}
				} else {
					trafficController.updateWagonStatus(wagon, true);
				}
			}
			// Si voie de départ != voie de formation, on libère la voie de
			// formation.
			if (otherDepartureRail) {
				Rail formationRail = trafficController.getRailById(sillon
						.getRailFormationId());
				formationRail.setAvailable(true);
				daoRail.updateForAvailable(formationRail);
			}
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_DEPARTURE);
			updateTraffic(trafficController);
			daoSillon.update(sillon);

		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (WagonPosTheoriqueException e) {
			refreshHub(trafficController);
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Dernière méthode pour la gestion des départs. On passe le statut des
	 * wagons à "Parti Gevrey", on efface les wagons de la voie de départ, on
	 * rend la voie de départ à nouveau disponible et on met à jour le statut du
	 * sillon qui passe à "Départ train terminé".
	 */
	@Override
	@Transactional
	public void completeDeparture(HubTrafficController trafficController,
			Sillon sillon) throws ApplicationException {

		// Récupération de la voie de départ.
		Rail departureRail = trafficController.getRailById(sillon.getRailId());
		try {
			List<Wagon> lstWagons = daoHub
					.getListWagonTrafficComposition(sillon);

			// On passe le statut des wagons à "Parti du Hub".
			for (Wagon wagon : lstWagons) {
				wagon.getNextStatus().setId(
						HubWagonStatus.WAGON_DEPARTURE_COMPLETED.getValue());
				trafficController.updateWagonStatus(wagon, true);
			}
			// On flush le traffic.
			updateTraffic(trafficController);

			// On supprime tous les wagons de la voie de départ.
			for (Wagon wagon : lstWagons) {
				trafficController.deleteWagon(wagon);
			}
			getNextSillonStatus(sillon, HubCurrentAction.PROCESS_DEPARTURE);
			departureRail.setAvailable(true);
			// On flush le traffic.
			updateTraffic(trafficController);
			daoRail.update(departureRail);
			daoSillon.update(sillon);
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * On affiche le document pdf pour le relevé de composition. Bien que l'on
	 * ait la liste des compositions, on préfère ne conserver que le sillon et
	 * rechercher en base la liste: cela permet de vérifier la prise en compte
	 * des données éventuellement modifiées par le coordinateur.
	 */
	@Override
	public String displayCompositionPlanPdf(User user, Sillon sillon)
			throws ApplicationException {

		try {
			List<Composition> lstCompositions = daoHub.getPlan(sillon);
			return pdfManager.createPdfCompositionPlan(user, lstCompositions,
					sillon);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	// *******************************************
	// Partie : Méthodes générales.
	// *******************************************

	/**
	 * Vérification de la voie de reception / formation / reception. On vérifie
	 * principalement que la voie est disponible et qu'il reste de la longueur
	 * utile. On récupère la liste des wagons dans la composition, on envoi
	 * l'ensemble des wagons dans le hub pour avoir les contrôles (si possible).
	 * A la moindre erreur on stoppe et surtout on redemande le rafraichissement
	 * du hub pour effacer les traces du test. On en profite pour injecter dans
	 * le sillon toutes les informations complémentaires sur la voie (
	 * identifiant externe, libellé de la voie).
	 */
	@Override
	public void checkRail(HubTrafficController trafficController,
			Sillon sillon, int railId, int typeCheckRail)
			throws ApplicationException {

		// Premier test de base.
		trafficController.checkRailAvailable(railId);

		// Récupération du rail
		Rail rail = trafficController.getRailById(railId);

		// Récupération du label pour la voie.
		String strRailLabel = (rail.getTypeBundle().getId() == HubBundleType.SORT
				.getValue() ? rail.getDestinationLabel() : rail.getTypeBundle()
				.getLabel());

		// On réinjecte l'identifiant du rail dans le sillon, dans le bon champ
		// en fonction du sillon (départ / arrivée / formation). et on complète
		// les informations du sillon concernant la voie.
		if (typeCheckRail == HubCurrentAction.PROCESS_FORMATION.getValue()) {
			sillon.setRailFormationId(rail.getId());
			sillon.setRailFormationExtId(rail.getNumber());
			sillon.setRailFormationLabel(strRailLabel);
		}
		// Dans tous les cas on injecte car par défaut voie de départ = voie de
		// formation.
		sillon.setRailId(rail.getId());
		sillon.setRailExtId(rail.getNumber());
		sillon.setRailLabel(strRailLabel);

		// Second test de base.
		if (rail.getLstWagons().size() > 0)
			throw new RailNotEmptyException(
					"Hub : La voie selectionnée n'est pas vide.");

		// Si programme de formation on s'arrete à ce niveau.
		if (typeCheckRail != HubCurrentAction.PROCESS_FORMATION.getValue()) {
			String errorMsg = null;
			try {
				List<Composition> lstComposition = daoHub.getPlan(sillon);
				for (Composition composition : lstComposition) {
					try {
						composition.getWagon().getNextPos()
								.setPosRailId(railId);
						trafficController.moveWagon(composition.getWagon(),
								HubStack.POS_END, false);
					} catch (WagonPosTheoriqueException e) {
						errorMsg = MessageUtils
								.getStringMessageError("hub.error.rail.wagon.theo");
						break;
					} catch (ApplicationException e) {
						errorMsg = e.getMessage();
						break;
					}
				}
			} catch (DataAccessException e) {
				throw new DaoApplicationException(e);
			} catch (Exception e) {
				throw new ApplicationException(e);
			} finally {
				// Tres important !!!
				refreshHub(trafficController);
			}
			if (null != errorMsg)
				throw new ApplicationException(errorMsg);
		}
	}

	/**
	 * Injection de la table hub_traffic dans le HubController. La table
	 * hub_traffic contient l'ensemble des wagons qui sont répartis sur le hub.
	 * On récupère donc une liste de wagons avec leur positionnement sur la voie
	 * ainsi que leur statut et leur destination (entre autres..). A la
	 * différence de la méthode "getHub" juste au dessus, les voies ne sont pas
	 * recréées, elles sont juste vidées avant réinsertion des wagons.
	 */
	public void refreshHub(HubTrafficController trafficController)
			throws ApplicationException {

		try {
			trafficController.setTraffic(daoHub.getTraffic());
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Une des méthodes les plus importantes du hub. On récupère la liste des
	 * voies et on la "flush" dans la base de données pour avoir la nouvelle
	 * position de tous les wagons.
	 */
	@Override
	public void updateTraffic(HubTrafficController trafficController)
			throws ApplicationException {
		try {
			daoHub.updateTraffic(trafficController.getLstRails());
			refreshHub(trafficController);
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Cette méthode permet de gérer le déplacement d'un wagon à l'unité. On
	 * déplace le wagon d'une voie à une autre ou plus simplement on change sa
	 * position au niveau de la voie. Selon la demande utilisateur (positionner
	 * le wagon au début , à la fin ou à une certaine position), on appelle la
	 * méthode correspondante au niveau du trafficControler.
	 */
	@Override
	public void moveWagon(HubTrafficController trafficController, Wagon wagon,
			HubStack stack) throws ApplicationException {

		try {
			trafficController.moveWagon(wagon, stack, false);
		} catch (ApplicationException e) {
			wagon.setErrorMessage(e.getMessage());
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupération de la liste des statuts pour un wagon. Il n'existe pas de
	 * manager ni de DAO pour les wagons, on passe directement par ceux du Hub.
	 */
	@Override
	public List<WagonStatus> getListWagonStatus(HubWagonTrailer trailer)
			throws ApplicationException {

		try {
			return daoHub.getListWagonStatus(trailer);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupération de la liste des types pour les wagons / locomotives. Il
	 * n'existe pas de manager ni de DAO pour les wagons, on passe directement
	 * par ceux du Hub.
	 */
	@Override
	public List<WagonType> getListWagonTypes(HubWagonTrailer trailer)
			throws ApplicationException {

		try {
			return daoHub.getListWagonTypes(trailer);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupération de la liste des wagons pour une voie donnée. Il s'agit de la
	 * photo de la voie à un instant t.
	 */
	@Override
	public List<Wagon> getListWagonsForRail(Rail rail)
			throws ApplicationException {
		try {
			return daoHub.getListWagonsForRail(rail);
		} catch (EmptyResultDataAccessException e) {
			throw new WagonNotFoundException(
					"Hub : Impossible de trouver des wagons pour cette voie.");
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Inversion du sens à l'arrivée ou au départ pour une rame. On met à jour
	 * l'indicateur d'inversion de sens dans la table des sillons pour le sillon
	 * concerné et on inverse la position de tous les wagons dans la table des
	 * compositions pour la composition concernée ( sur 22 wagons celui en
	 * position 22 passe en position 0 etc...)
	 */
	@Override
	@Transactional
	public void reverseSense(Sillon sillon) throws ApplicationException {
		try {
			daoHub.updateReverseSense(sillon);
			daoSillon.update(sillon);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Suppression d'un wagon ou d'une locomotive dans le traffic. Il faut
	 * vérifier que le wagon ne fasse pas partie d'une composition au départ. Si
	 * oui, alors on doit supprimer le wagon de la composition. On supprime le
	 * wagon et on redemande l'affichage du hub.
	 */
	@Override
	@Transactional
	public void deleteWagon(HubTrafficController trafficController, Wagon wagon)
			throws ApplicationException {
		try {
			if (wagon.getSillonId() > 0) {
				// On regarde si le sillon est un sillon de départ.
				Sillon sillon = daoSillon.getById(wagon.getSillonId());
				if (HubSillonType.DEPARTURE.getValue().equals(sillon.getType()))
					deleteWagonInPlan(sillon, wagon);
			}
			// Suppression du wagon en position réelle.
			trafficController.deleteWagon(wagon);
			// On regarde si wagon en position théorique.
			Wagon wagonInTraffic = findWagonInTraffic(trafficController,
					wagon.getId(), false);
			if (wagonInTraffic != null) {
				if (!wagonInTraffic.getCurrentPos().isPosReal())
					trafficController.deleteWagon(wagonInTraffic);
				else
					throw new ApplicationException(
							"Hub : Impossible de supprimer le wagon.");
			}
			updateTraffic(trafficController);
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Suppression d'un wagon déjà présent dans une composition. On récupère le
	 * sillon, à partir du sillon, on récupère la liste des compositions et on
	 * itère sur la liste des compositions jusqu'a retrouver le wagon. A partir
	 * de la position du wagon dans la liste, on décrémente tous les wagons
	 * suivants.
	 */
	private void deleteWagonInPlan(Sillon sillon, Wagon wagon)
			throws ApplicationException {
		try {
			Composition compositionPivot = null;
			// Il faut supprimer le wagon de la composition et
			// renuméroter la composition.
			List<Composition> lstCompositions = daoHub.getPlan(sillon);
			// Il faut retrouver le wagon par l'id car sa position sur
			// la voie ne correspond pas forcement à sa position dans la
			// composition.
			for (Composition composition : lstCompositions) {
				if (wagon.getId() == composition.getWagon().getId()) {
					compositionPivot = composition;
					daoComposition.delete(composition);
				} else if (compositionPivot != null
						&& (composition.getId() > compositionPivot.getId())) {
					int newPosNum = composition.getWagon().getCurrentPos()
							.getPosNum() - 1;
					composition.getWagon().getCurrentPos().setPosNum(newPosNum);
					daoComposition.update(composition);
				}
			}
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Récupération d'un wagon dans le traffic sur le hub. Il s'agit pour le
	 * coordinateur de pouvoir retrouver facilement la position d'un wagon dans
	 * le traffic à partir de son identifiant externe (le seul qu'il connaisse).
	 * On ne va donc pas rechercher en base, une simple itération dans la liste
	 * des rails est suffisante. On boucle sur toute la liste pour placer les
	 * précédentes recherche à "false".
	 */
	@Override
	public String findWagonInTraffic(HubTrafficController trafficControler,
			String wagonExtId) throws ApplicationException {

		boolean wagonFounded = false;
		StringBuffer strRail = new StringBuffer();
		for (Rail rail : trafficControler.getLstRails()) {
			for (Wagon wagon : rail.getLstWagons()) {
				wagon.setInquired(false);
				if (wagon.getExtId().equals(wagonExtId)) {
					if (strRail.length() > 0)
						strRail.append(" ,");
					strRail.append(rail.getNumber()).append(" ");
					if (rail.getTypeBundle().getId() == HubBundleType.SORT
							.getValue()) {
						strRail.append(rail.getDestinationLabel());
					} else {
						strRail.append(rail.getTypeBundle().getLabel());
					}
					wagon.setInquired(true);
					wagonFounded = true;
				}
			}
		}
		if (!wagonFounded)
			throw new WagonNotFoundException("Hub : aucun wagon "
					+ "trouvé avec cet référence");
		return strRail.toString();
	}

	/**
	 * Retrouve un wagon dans le traffic (donc directement dans le
	 * trafficController) à partir de son identifiant interne. Si le wagon est
	 * trouvé, on retourne le wagon. On recherche soit un wagon en position
	 * réelle, soit un wagon en position théorique.
	 */
	private Wagon findWagonInTraffic(HubTrafficController trafficController,
			int wagonId, boolean posReal) throws ApplicationException {
		for (Rail rail : trafficController.getLstRails()) {
			for (Wagon wagonTraffic : rail.getLstWagons()) {
				if (wagonId == wagonTraffic.getId()
						&& posReal == wagonTraffic.getCurrentPos().isPosReal())
					return wagonTraffic;
			}
		}
		return null;
	}

	/**
	 * Ajout d'un wagon ou d'un locomotive dans le traffic. Ici aucun contrôle
	 * n'est effectué. On vérifie simplement si le wagon ou la locomotive est
	 * déjà présent dans la base de données, si ce n'est pas le cas, alors on
	 * l'ajoute. On vérifie aussi que le wagon n'est pas déjà présent dans le
	 * traffic.
	 */
	@Override
	@Transactional
	public void addWagon(HubTrafficController trafficController, Wagon wagon)
			throws ApplicationException {

		try {
			try {
				Wagon baseWagon = daoHub.getWagonByExtId(wagon.getExtId());
				// Si trouvé on recopie les données (la base prévaut)
				int railId = wagon.getNextPos().getPosRailId();
				int posNum = wagon.getNextPos().getPosNum();
				int statusId = wagon.getNextStatus().getId();
				String fuelLevel = wagon.getFuelLevel();
				wagon = (Wagon) baseWagon.clone();
				wagon.getNextPos().setPosRailId(railId);
				wagon.getNextPos().setPosNum(posNum);
				wagon.getNextStatus().setId(statusId);
				wagon.setFuelLevel(fuelLevel);
			} catch (EmptyResultDataAccessException e) {
				// Pour les locos on met un riv par défaut + SO dans lading.
				if (!wagon.getType().isTrailer()) {
					wagon.setRiv(HubWagonRiv.NOT_RIV.getValue());
					wagon.setLading(LabelUtils.getLabel("common.so"));
				}
				// Insertion du wagon dans la base.
				wagon.setId(daoHub.insertWagon(wagon));
			}
			// On regarde si le wagon est déjà présent dans le traffic (position
			// réelle).
			Wagon wagonInTrafficReal = findWagonInTraffic(trafficController,
					wagon.getId(), true);

			// On regarde si le wagon est déjà présent dans le traffic (position
			// théorique).
			Wagon wagonInTrafficTheo = findWagonInTraffic(trafficController,
					wagon.getId(), false);

			if (wagonInTrafficReal != null || wagonInTrafficTheo != null)
				throw new WagonAlreadyInTrafficException(
						"Hub : Le wagon est déjà présent dans le traffic.");

			// Dans tous les cas, il faut initialiser les données minimales
			// pour l'insertion dans la table hub_traffic.
			wagon.setFromRail(null);
			wagon.setSillonId(0);

			// Si aucune destination , destination par defaut
			if (wagon.getDestinationId() < 1)
				wagon.setDestinationId(1);

			// Récupération du rail.
			Rail rail = trafficController.getRailById(wagon.getNextPos()
					.getPosRailId());

			// Pour préserver l'homogénéité le statut est dans NextStatus au
			// niveau de l'IHM mais en dans ce cas, il faut le placer
			// directement dans currentStatus.
			wagon.getCurrentStatus().setId(wagon.getNextStatus().getId());

			// Insertion du wagon dans le traffic.
			HubStack stack;
			if (wagon.getNextPos().getPosNum() <= 0) {
				stack = HubStack.POS_BEGIN;
			} else if (wagon.getNextPos().getPosNum() < rail.getLstWagons()
					.size()) {
				stack = HubStack.POS_AT;
			} else {
				stack = HubStack.POS_END;
			}

			// Placement du wagon.
			trafficController.moveWagonAExOnly(wagon, stack, true);
			// Mise à jour du traffic.
			updateTraffic(trafficController);

		} catch (WagonPosTheoriqueException e) {
			throw e;
		} catch (WagonAlreadyInTrafficException e) {
			throw e;
		} catch (ApplicationException e) {
			refreshHub(trafficController);
			throw e;
		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Mise à jour d'un wagon. Il est possible de mettre à jour le statut du
	 * wagon, de le déplacer de voie et ou de position, de placer un commentaire
	 * sur le wagon. On ne peut déplacer un wagon sur une voie indisponible ou
	 * si le wagon à déjà un positionnement théorique.
	 */
	@Override
	public void updateWagon(HubTrafficController trafficController, Wagon wagon)
			throws ApplicationException {

		try {

			// Interdiction si la voie n'est pas disponible.
			trafficController.checkRailAvailable(wagon.getNextPos()
					.getPosRailId());

			// Positionne le wagon au début ou à une position particulière.
			HubStack stack = (wagon.getNextPos().getPosNum() == 0) ? HubStack.POS_BEGIN
					: HubStack.POS_AT;

			// On demande un changement de statut.
			if (wagon.getCurrentStatus().getId() != wagon.getNextStatus()
					.getId()) {
				trafficController.updateWagonStatus(wagon, true);
			}

			// On demande un changement de commentaire.
			trafficController.updateWagonForComment(wagon);

			// On demande un changement de voie
			if (wagon.getCurrentPos().getPosRailId() != wagon.getNextPos()
					.getPosRailId()) {

				// On positionne le wagon.
				trafficController.moveWagonAExOnly(wagon, stack, true);
				trafficController.deleteWagon(wagon);

				// On vérifie si le wagon avait une position théorique.
				Wagon wagonTheo = findWagonInTraffic(trafficController,
						wagon.getId(), false);

				// Si oui, on annule la position théorique du wagon.
				if (null != wagonTheo)
					trafficController.deleteWagon(wagonTheo);

				// On demande un changement de position.
			} else if (wagon.getCurrentPos().getPosNum() != wagon.getNextPos()
					.getPosNum()) {

				// On supprime le wagon, puis on le bouge.
				trafficController.deleteWagon(wagon);
				trafficController.moveWagonAExOnly(wagon, stack, true);
			}

			// Mise à jour du traffic.
			updateTraffic(trafficController);
		} catch (FunctionnalApplicationException e) {
			throw e;

		} catch (DataAccessException e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);

		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Déplacement de plusieurs wagons, on prend chaque wagon, on vérifie s'il
	 * est en position réelle, on le déplace et on modifie son statut. A la
	 * moindre erreur rencontrée, on annule toutes les mofifications. Le
	 * déplacement place le wagon en position réelle, il faut donc supprimer le
	 * wagon de sa position actuelle.
	 */
	@Override
	public void updateWagons(HubTrafficController trafficController, Rail rail,
			int targetRailId, int statusId, int nbWagonsStack)
			throws ApplicationException {

		// Vérifie voie disponible.
		trafficController.checkRailAvailable(targetRailId);
		// Vérifie que la voie contient bien le bon nombre de wagons.
		if (rail.getLstWagons().size() < nbWagonsStack)
			throw new WagonNumberException("Hub : Nombre de wagons trop grand");

		// Il faut recopier la liste des wagons sinon il ne sera pas possible de
		// les supprimer car on travaillerait directement sur la voie. On en
		// profite pour vérifier si les wagons sont en position théorique.
		List<Wagon> lstWagons = new ArrayList<Wagon>();
		for (Wagon wagon : rail.getLstWagons()) {
			lstWagons.add((Wagon) wagon.clone());
			if (wagon.getCurrentPos().getPosNum() <= (nbWagonsStack - 1)
					&& !wagon.getCurrentPos().isPosReal())
				throw new WagonPosTheoriqueException(
						"Hub : La wagon est en position théorique.");
		}
		// Maintenant on peut travailler sur la liste qui est dissociée.
		try {
			for (Wagon wagon : lstWagons) {
				if (nbWagonsStack > 0) {
					wagon.getNextPos().setPosRailId(targetRailId);
					wagon.getCurrentStatus().setId(statusId);
					trafficController.moveWagonAExOnly(wagon, HubStack.POS_END,
							true);
					trafficController.deleteWagon(wagon);
					nbWagonsStack--;
				}
			}
			updateTraffic(trafficController);
		} catch (Exception e) {
			refreshHub(trafficController);
			throw new ApplicationException(e);
		}
	}

	/**
	 * Vérifie si le wagon n'est pas déjà présent dans un programme de
	 * formation. On récupère l'identifiant de sillon et on regarde s'il s'agit
	 * d'un sillon de départ. (on aurait aussi pu prendre le TCMS TRAIN ID pour
	 * faire la recherche).
	 */
	@Override
	public void checkWagonNotInFormationPlan(Wagon wagon)
			throws ApplicationException {

		if (wagon.getSillonId() > 0) {
			try {
				Sillon sillon = daoSillon.getById(wagon.getSillonId());
				if (HubSillonType.DEPARTURE.getValue().equals(sillon.getType()))
					throw new WagonAlreadyInFormationPlanException(
							"Hub : Le wagon est déjà présent dans un plan de formation.");
			} catch (WagonAlreadyInFormationPlanException e) {
				throw e;
			} catch (EmptyResultDataAccessException e) {
				// RAS.
			} catch (DataAccessException e) {
				throw new DaoApplicationException(e);
			} catch (Exception e) {
				throw new ApplicationException(e);
			}
		}
	}

	/**
	 * Tente de trouver une autre voie pour un wagon (création du programme de
	 * travail) si la voie préalablement selectionnée lors de l'intégration du
	 * fichier de composition est indisponible au moment ou le coordinateur
	 * demande la création effective du programme de travail.
	 */
	private void getOtherRailForWagon(HubTrafficController trafficController,
			Wagon wagon) throws ApplicationException {

		for (Rail rail : trafficController.getLstCenterRails()) {
			if (rail.getDestinationId() == wagon.getDestinationId()
					&& rail.isAvailable()) {
				wagon.getNextPos().setPosRailId(rail.getId());
				break;
			}
		}
	}

	/**
	 * Mise à jour de la disponibilité d'une voie. Plus simple que de faire
	 * passer le coordinateur par le panneau de configuration des voies.
	 */
	@Override
	public void updateRailAvailable(Rail rail) throws ApplicationException {

		try {
			daoRail.updateForAvailable(rail);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		}
	}

	/**
	 * Envoi d'un mail avec le relevé de composition en pièce jointe. On
	 * récupère le relevé de composition, si non trouvé on lance une excepion.
	 * Comme cette action intervient automatiquement après la validation du
	 * relevé, on est certain que le relevé qui va être envoyé est bien celui
	 * qui vient d'être validé par le coordinateur.
	 */
	@Override
	public void sendCompositionMail(User user) throws ApplicationException {

		try {
			mailManager.sendMultiPartMail(user,
					PdfManagerImpl.PDF_COMPOSITION_PLAN);
		} catch (FileNotFoundException e) {
			throw new MailNotSendException(e);
		} catch (MessagingException e) {
			throw new MailNotSendException(e);
		} catch (Exception e) {
			throw new MailNotSendException(e);
		}
	}

	// *******************************************
	// Partie : Intégration TCMS.
	// *******************************************

	/**
	 * Intégration d'une liste de sillons à partir de fichiers reçus de TCMS. On
	 * récupère la liste des sillons et on l'envoi au dao pour soit une
	 * insertion, soit une mise à jour du sillon.
	 */
	@Override
	public void integrateSillons(List<Sillon> lstSillons)
			throws ApplicationException {
		try {
			daoSillon.integrate(lstSillons);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}

	/**
	 * Intégration de la liste des compositions pour un sillon à l'arrivée. On
	 * récupère la liste des compositions et on l'envoi au dao. On récupère le
	 * sillon, puis on insere la liste des compositions, enfin, on met le sillon
	 * à jour avec les données complémentaires (passe entre autres le statut du
	 * sillon à "Revelé de composition arrivé, train non arrivé").
	 * 
	 * On vérifie que le sillon existe (sinon problème cela signifie que le
	 * train est déjà en route vers le hub et que l'on a toujours pas le
	 * sillon.)
	 * 
	 * On vérifie que le programme de travail n'a pas été validé (sinon signifie
	 * que l'on va écraser un programme de travail alors que des wagons sont
	 * déjà en position réelle dans le hub.)
	 */
	@Override
	@Transactional
	public void integrateComposition(List<Composition> lstCompositions,
			String tcmsTrainId, String fileStoreName)
			throws ApplicationException {

		try {
			int nbEmpty = 0;
			int nbLoaded = 0;
			HubLading sillonLading;

			// Récupération du sillon pour la composition.
			Sillon sillon = daoSillon.getByTcmsTrainId(tcmsTrainId);

			// Test supplémentaire au EmptyResultDataAccessException
			if (!HubSillonType.ARRIVAL.getValue().equals(sillon.getType()))
				throw new SillonNotFoundException(
						"Hub : Le sillon n'est pas du bon type.");

			// On vérifie le statut du sillon.
			if (sillon.getStatusId() > HubSillonStatus.WORKING_PLAN_REQUESTED_NO_TRAIN
					.getValue())
				throw new SillonAlreadyProcededException(
						"Hub : Le programme de travail est déjà en cours pour ce sillon.");

			// Si le sillon à déjà une composition, on la supprime (plus simple
			// que de faire des updates etc...).
			daoComposition.deleteAll(sillon.getId());

			// On lie la composition avec le sillon et on en profite pour
			// mettre à jour le sillon avec la donnée de chargement.
			for (Composition composition : lstCompositions) {
				composition.setSillonId(sillon.getId());
				if (HubLading.LOADED.getValue().equals(
						composition.getWagon().getLading())) {
					nbLoaded++;
				} else {
					nbEmpty++;
				}
			}

			// Mise à jour de l'indicateur de chargement pour le sillon.
			if (nbEmpty > 0 && nbLoaded > 0) {
				sillonLading = HubLading.MIXED;
			} else if (nbLoaded > 0) {
				sillonLading = HubLading.LOADED;
			} else {
				sillonLading = HubLading.EMPTY;
			}

			// On insère la composition.
			daoComposition.integrate(lstCompositions);

			// On met à jour le sillon.
			sillon.setCompositionFileName(fileStoreName);
			sillon.setStatusId(HubSillonStatus.COMPO_PLAN_ARRIVED_NO_TRAIN
					.getValue());

			// Si le sillon ne contient que des wagons vides, il n'y a pas
			// besoin de faire le programme de travail car le train repart
			// directement de la voie de réception. (+ lisible que if ..
			// else...etc..)
			if (HubLading.EMPTY == sillonLading)
				sillon.setStatusId(HubSillonStatus.WORKING_PLAN_EXCLUDED
						.getValue());

			// Complétion des données sillon.
			sillon.setNbWagons(lstCompositions.size());
			sillon.setLading(sillonLading.getValue());

			// Mise à jour du sillon dans la base de données.
			daoSillon.update(sillon);
		} catch (FunctionnalApplicationException e) {
			throw e;
		} catch (EmptyResultDataAccessException e) {
			throw new SillonNotFoundException(e);
		} catch (DataAccessException e) {
			throw new DaoApplicationException(e);
		} catch (Exception e) {
			throw new ApplicationException(e);
		}
	}
}
